use std::ops::{Deref, DerefMut};

use mark_dirty::DirtyMark;

use crate::scene::{AsBytes, Vertex};

use super::buffer::DynBuffer;

pub struct Mesh<V: Vertex + AsBytes> {
    vertices: Vec<V>,
    indices: Vec<u32>,
}

impl<V: Vertex + AsBytes> Mesh<V> {
    pub fn new() -> Self {
        Self {
            vertices: vec![],
            indices: vec![],
        }
    }

    pub fn vertices(&self) -> &[V] {
        &self.vertices
    }

    pub fn indices(&self) -> &[u32] {
        &self.indices
    }

    pub fn vertices_mut(&mut self) -> &mut Vec<V> {
        &mut self.vertices
    }

    pub fn indices_mut(&mut self) -> &mut Vec<u32> {
        &mut self.indices
    }

    pub fn vertex_count(&self) -> u32 {
        self.vertices().len() as u32
    }

    pub fn index_count(&self) -> u32 {
        self.indices().len() as u32
    }

    pub fn clear(&mut self) {
        self.vertices_mut().clear();
        self.indices_mut().clear();
    }

    pub fn add_vertex(&mut self, vertex: V) {
        let count = self.vertex_count();
        self.vertices_mut().push(vertex);
        self.indices_mut().push(count);
    }

    pub fn add_triangle(&mut self, triangle: [V; 3]) {
        let count = self.vertex_count();
        self.vertices_mut().extend(triangle);
        self.indices_mut().extend([count, count + 1, count + 2]);
    }

    pub fn add_quad(&mut self, quad: [V; 4]) {
        let count = self.vertex_count();
        self.vertices_mut().extend(quad);
        self.indices_mut()
            .extend([count, count + 1, count + 2, count, count + 2, count + 3]);
    }
}

pub struct MeshInstance<V: Vertex + AsBytes> {
    mesh: DirtyMark<Mesh<V>>,
    vertex_buffer: DynBuffer<V>,
    index_buffer: DynBuffer<u32>,
}

impl<V: Vertex + AsBytes> DerefMut for MeshInstance<V> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.mesh
    }
}

impl<V: Vertex + AsBytes> Deref for MeshInstance<V> {
    type Target = Mesh<V>;

    fn deref(&self) -> &Self::Target {
        &self.mesh
    }
}

impl<V: Vertex + AsBytes> MeshInstance<V> {
    pub fn new(ctx: &super::Context) -> Self {
        Self::with_capacity(ctx, 0, 0)
    }

    pub fn with_capacity(ctx: &super::Context, vertex_count: usize, index_count: usize) -> Self {
        let vertex_buffer = DynBuffer::new(ctx, vertex_count, wgpu::BufferUsages::VERTEX);
        let index_buffer = DynBuffer::new(ctx, index_count, wgpu::BufferUsages::INDEX);

        Self {
            mesh: DirtyMark::new(Mesh::new()),
            vertex_buffer,
            index_buffer,
        }
    }

    pub fn prepare(&mut self, ctx: &super::Context) {
        if self.mesh.is_dirty() {
            self.vertex_buffer.write(ctx, self.mesh.vertices());
            self.index_buffer.write(ctx, self.mesh.indices());

            self.mesh.unmark_dirty();
        }
    }

    pub fn render<'p>(&'p self, pass: &mut wgpu::RenderPass<'p>) {
        pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
        pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
        pass.draw_indexed(0..self.mesh.index_count(), 0, 0..1);
    }
}
